﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using ActiproSoftware.SyntaxEditor;
using ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast;
// using ActiproSoftware.SyntaxEditor.Addons.Xml.Ast;

namespace WindowsUserControl.Codes
{
    /// <summary>
    /// Provides a class for updating a document outline <see cref="TreeView"/> with nodes from various language ASTs.
    /// </summary>
    public class DocumentOutlineUpdater
    {

        private TreeView documentOutlineTreeView;
        private string guid;
        private TreeNode parentTreeNode;
        private ArrayList treeNodeChildren;
        private ArrayList treeNodeIndices;

        /////////////////////////////////////////////////////////////////////////////////////////////////////
        // OBJECT
        /////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Initializes a new instance of the <c>DocumentOutlineUpdater</c> class. 
        /// </summary>
        /// <param name="guid">The GUID to examine.</param>
        /// <param name="documentOutlineTreeView">The <see cref="TreeView"/> to update.</param>
        public DocumentOutlineUpdater(string guid, TreeView documentOutlineTreeView)
        {
            // Initialize parameters
            this.guid = guid;
            this.documentOutlineTreeView = documentOutlineTreeView;
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////
        // NON-PUBLIC PROCEDURES
        /////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Gets or sets the current tree node index.
        /// </summary>
        /// <value>The current tree node index.</value>
        private int CurrentTreeNodeIndex
        {
            get
            {
                return (int)treeNodeIndices[0];
            }
            set
            {
                treeNodeIndices[0] = value;
            }
        }

        /// <summary>
        /// Ends the current node and optionally moves up a level.
        /// </summary>
        /// <param name="moveUp">Whether to move up in the <see cref="TreeNode"/> context.</param>
        private void EndNode(bool moveUp)
        {
            // Remove any inappropriate child nodes
            if ((!moveUp) && (parentTreeNode != null) && (this.CurrentTreeNodeIndex > 0) && (this.CurrentTreeNodeIndex - 1 < parentTreeNode.Nodes.Count))
                this.RemoveChildNodes(parentTreeNode.Nodes[this.CurrentTreeNodeIndex - 1], this.ParentTreeNodeChildCount);

            // Remove the child count for the node 
            treeNodeChildren.RemoveAt(0);

            if (moveUp)
            {
                if (parentTreeNode != null)
                {
                    // Remove any remaining nodes
                    this.RemoveChildNodes(parentTreeNode, this.CurrentTreeNodeIndex);

                    // Remove the current level's index
                    treeNodeIndices.RemoveAt(0);

                    // Move up a level
                    parentTreeNode = parentTreeNode.Parent;
                }
                else
                {
                    // Remove any remaining nodes
                    while (this.CurrentTreeNodeIndex < documentOutlineTreeView.Nodes.Count)
                        documentOutlineTreeView.Nodes.RemoveAt(this.CurrentTreeNodeIndex);
                }
            }
        }

        /// <summary>
        /// Ends the root node.
        /// </summary>
        private void EndRoot()
        {
            this.EndNode(true);
#if !NET11
            if ((documentOutlineTreeView.Tag != (object)guid) && (documentOutlineTreeView.Nodes.Count > 0))
                documentOutlineTreeView.TopNode = documentOutlineTreeView.Nodes[0];
#endif
            documentOutlineTreeView.EndUpdate();
        }

        /// <summary>
        /// Gets or sets the parent tree node child count.
        /// </summary>
        /// <value>The parent tree node child count.</value>
        private int ParentTreeNodeChildCount
        {
            get
            {
                return (int)treeNodeChildren[0];
            }
            set
            {
                treeNodeChildren[0] = value;
            }
        }

        /// <summary>
        /// Processes the child nodes of the specified <see cref="IAstNode"/>.
        /// </summary>
        /// <param name="compilationUnit">The parent <see cref="ICompilationUnit"/>.</param>
        /// <param name="node">The <see cref="IAstNode"/> to examine.</param>
        private void ProcessChildNodes(ICompilationUnit compilationUnit, IAstNode node)
        {
            // Recurse
            if (node.ChildNodeCount > 0)
            {
                if (node is ICompilationUnit)
                    compilationUnit = (ICompilationUnit)node;

                foreach (IAstNode childNode in node.ChildNodes)
                    this.ProcessNode(compilationUnit, childNode);
            }
        }

        /// <summary>
        /// Processes the specified <see cref="IAstNode"/>.
        /// </summary>
        /// <param name="compilationUnit">The parent <see cref="ICompilationUnit"/>.</param>
        /// <param name="node">The <see cref="IAstNode"/> to examine.</param>
        private void ProcessDotNetNode(ICompilationUnit compilationUnit, ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast.AstNode node)
        {
            // Process a node start
            switch (node.NodeType)
            {
                case DotNetNodeType.CompilationUnit:
                    if (node.ParentNode == null)
                        this.StartRoot();
                    break;
                case DotNetNodeType.ClassDeclaration:
                case DotNetNodeType.EnumerationDeclaration:
                case DotNetNodeType.InterfaceDeclaration:
                case DotNetNodeType.NamespaceDeclaration:
                case DotNetNodeType.StandardModuleDeclaration:
                case DotNetNodeType.StructureDeclaration:
                    this.StartNode(true, node);
                    break;
                case DotNetNodeType.ExternAliasDirectiveSection:
                    if (((ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast.ExternAliasDirectiveSection)node).Directives.Count > 0)
                        this.StartNode(true, node);
                    break;
                case DotNetNodeType.UsingDirectiveSection:
                    if (((ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast.UsingDirectiveSection)node).Directives.Count > 0)
                        this.StartNode(true, node);
                    break;
                case DotNetNodeType.ConstructorDeclaration:
                case DotNetNodeType.DelegateDeclaration:
                case DotNetNodeType.DestructorDeclaration:
                case DotNetNodeType.EnumerationMemberDeclaration:
                case DotNetNodeType.EventDeclaration:
                case DotNetNodeType.ExternAliasDirective:
                case DotNetNodeType.FixedSizeBufferDeclarator:
                case DotNetNodeType.InterfaceEventDeclaration:
                case DotNetNodeType.InterfaceMethodDeclaration:
                case DotNetNodeType.InterfacePropertyDeclaration:
                case DotNetNodeType.MethodDeclaration:
                case DotNetNodeType.OperatorDeclaration:
                case DotNetNodeType.PropertyDeclaration:
                case DotNetNodeType.UsingDirective:
                case DotNetNodeType.VariableDeclarator:
                    this.StartNode(false, node);
                    if ((compilationUnit != null) && (!compilationUnit.HasLanguageTransitions))
                        return;
                    break;
            }

            // Process child nodes
            this.ProcessChildNodes(compilationUnit, node);

            // Process a node end
            switch (node.NodeType)
            {
                case DotNetNodeType.CompilationUnit:
                    if (node.ParentNode == null)
                        this.EndRoot();
                    break;
                case DotNetNodeType.ClassDeclaration:
                case DotNetNodeType.EnumerationDeclaration:
                case DotNetNodeType.InterfaceDeclaration:
                case DotNetNodeType.NamespaceDeclaration:
                case DotNetNodeType.StandardModuleDeclaration:
                case DotNetNodeType.StructureDeclaration:
                    this.EndNode(true);
                    break;
                case DotNetNodeType.ExternAliasDirectiveSection:
                    if (((ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast.ExternAliasDirectiveSection)node).Directives.Count > 0)
                        this.EndNode(true);
                    break;
                case DotNetNodeType.UsingDirectiveSection:
                    if (((ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast.UsingDirectiveSection)node).Directives.Count > 0)
                        this.EndNode(true);
                    break;
                case DotNetNodeType.ConstructorDeclaration:
                case DotNetNodeType.DelegateDeclaration:
                case DotNetNodeType.DestructorDeclaration:
                case DotNetNodeType.EnumerationMemberDeclaration:
                case DotNetNodeType.EventDeclaration:
                case DotNetNodeType.ExternAliasDirective:
                case DotNetNodeType.InterfaceEventDeclaration:
                case DotNetNodeType.InterfaceMethodDeclaration:
                case DotNetNodeType.InterfacePropertyDeclaration:
                case DotNetNodeType.MethodDeclaration:
                case DotNetNodeType.OperatorDeclaration:
                case DotNetNodeType.PropertyDeclaration:
                case DotNetNodeType.UsingDirective:
                    this.EndNode(false);
                    break;
                case DotNetNodeType.FixedSizeBufferDeclarator:
                case DotNetNodeType.VariableDeclarator:
                    this.EndNode(false);
                    break;
            }
        }

        /// <summary>
        /// Processes the specified <see cref="IAstNode"/>.
        /// </summary>
        /// <param name="compilationUnit">The parent <see cref="ICompilationUnit"/>.</param>
        /// <param name="node">The <see cref="IAstNode"/> to examine.</param>
        private void ProcessNode(ICompilationUnit compilationUnit, IAstNode node)
        {
            if (node is ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast.AstNode)
            {
                // Process .NET node
                this.ProcessDotNetNode(compilationUnit, (ActiproSoftware.SyntaxEditor.Addons.DotNet.Ast.AstNode)node);
            }
            else if (node is AstNode)
            {
                // Process Simple node
                this.ProcessSimpleNode(compilationUnit, (AstNode)node);
            }
            //else if (node is ActiproSoftware.SyntaxEditor.Addons.Xml.Ast.AstNode)
            //{
            //    // Process XML node
            //    this.ProcessXmlNode(compilationUnit, (ActiproSoftware.SyntaxEditor.Addons.Xml.Ast.AstNode)node);
            //}
            else if (node is LanguageTransitionAstNode)
            {
                // Process child nodes
                this.ProcessChildNodes(compilationUnit, node);
            }
            else
            {
                // Show all nodes for other language
                this.ProcessOtherLanguageNode(compilationUnit, node);
            }
        }

        /// <summary>
        /// Processes the specified <see cref="IAstNode"/>.
        /// </summary>
        /// <param name="compilationUnit">The parent <see cref="ICompilationUnit"/>.</param>
        /// <param name="node">The <see cref="IAstNode"/> to examine.</param>
        private void ProcessOtherLanguageNode(ICompilationUnit compilationUnit, IAstNode node)
        {
            // Process a node start
            if (node is ICompilationUnit)
            {
                if (node.ParentNode == null)
                    this.StartRoot();
            }
            else
                this.StartNode(true, node);

            // Process child nodes
            this.ProcessChildNodes(compilationUnit, node);

            // Process a node end
            if (node is ICompilationUnit)
            {
                if (node.ParentNode == null)
                    this.EndRoot();
            }
            else
                this.EndNode(true);
        }

        /// <summary>
        /// Processes the specified <see cref="IAstNode"/>.
        /// </summary>
        /// <param name="compilationUnit">The parent <see cref="ICompilationUnit"/>.</param>
        /// <param name="node">The <see cref="IAstNode"/> to examine.</param>
        private void ProcessSimpleNode(ICompilationUnit compilationUnit, AstNode node)
        {
            // Process a node start
            switch (node.NodeType)
            {
                case SimpleNodeType.CompilationUnit:
                    if (node.ParentNode == null)
                        this.StartRoot();
                    break;
                case SimpleNodeType.Identifier:
                    // Do nothing
                    break;
                default:
                    this.StartNode(true, node);
                    break;
            }

            // Process child nodes
            this.ProcessChildNodes(compilationUnit, node);

            // Process a node end
            switch (node.NodeType)
            {
                case SimpleNodeType.CompilationUnit:
                    if (node.ParentNode == null)
                        this.EndRoot();
                    break;
                case SimpleNodeType.Identifier:
                    // Do nothing
                    break;
                default:
                    this.EndNode(true);
                    break;
            }
        }

        

        /// <summary>
        /// Removes any child nodes after the specified count.
        /// </summary>
        /// <param name="node">The parent <see cref="TreeNode"/>.</param>
        /// <param name="count">The target count of child nodes.</param>
        private void RemoveChildNodes(TreeNode node, int count)
        {
            while (count < node.Nodes.Count)
                node.Nodes.RemoveAt(count);
        }

        /// <summary>
        /// Adds a new <see cref="TreeNode"/> to the <see cref="TreeView"/>.
        /// </summary>
        /// <param name="moveDown">Whether to move down in <see cref="TreeNode"/> context after the new <see cref="TreeNode"/> is added.</param>
        /// <param name="node">The <see cref="IAstNode"/> represented by the <see cref="TreeNode"/>.</param>
        private void StartNode(bool moveDown, IAstNode node)
        {
            this.StartNode(moveDown, node, node.DisplayText, node.ImageIndex);
        }

        /// <summary>
        /// Adds a new <see cref="TreeNode"/> to the <see cref="TreeView"/>.
        /// </summary>
        /// <param name="moveDown">Whether to move down in <see cref="TreeNode"/> context after the new <see cref="TreeNode"/> is added.</param>
        /// <param name="node">The <see cref="IAstNode"/> represented by the <see cref="TreeNode"/>.</param>
        /// <param name="text">The text.</param>
        /// <param name="imageIndex">The image index.</param>
        private void StartNode(bool moveDown, IAstNode node, string text, int imageIndex)
        {
            // Increase the child count
            this.ParentTreeNodeChildCount++;

            // Initialize a child count for the node 
            treeNodeChildren.Insert(0, 0);

            // Determine whether a new node is needed
            bool createNewNode = false;
            if (parentTreeNode == null)
            {
                if (this.CurrentTreeNodeIndex >= documentOutlineTreeView.Nodes.Count)
                    createNewNode = true;
            }
            else if (this.CurrentTreeNodeIndex >= parentTreeNode.Nodes.Count)
                createNewNode = true;

            TreeNode treeNode;
            if (createNewNode)
            {
                // Create a new node
                treeNode = new TreeNode();
                treeNode.Text = text;
                treeNode.ImageIndex = imageIndex;
                treeNode.SelectedImageIndex = imageIndex;
                treeNode.Tag = node;

                // Add the new node
                if (parentTreeNode == null)
                    documentOutlineTreeView.Nodes.Add(treeNode);
                else
                {
                    parentTreeNode.Nodes.Add(treeNode);
                    if (!parentTreeNode.IsExpanded)
                        parentTreeNode.Expand();
                }
            }
            else
            {
                // Get the existing node
                if (parentTreeNode == null)
                    treeNode = documentOutlineTreeView.Nodes[this.CurrentTreeNodeIndex];
                else
                {
                    treeNode = parentTreeNode.Nodes[this.CurrentTreeNodeIndex];
                }

                // Update the existing node
                if (treeNode.Text != text)
                    treeNode.Text = text;
                if (treeNode.ImageIndex != imageIndex)
                {
                    treeNode.ImageIndex = imageIndex;
                    treeNode.SelectedImageIndex = imageIndex;
                }
                treeNode.Tag = node;
            }

            // Increment the index
            this.CurrentTreeNodeIndex++;

            // If we are moving down a level...
            if (moveDown)
            {
                parentTreeNode = treeNode;
                treeNodeIndices.Insert(0, 0);
            }
        }

        /// <summary>
        /// Starts the root node.
        /// </summary>
        private void StartRoot()
        {
            documentOutlineTreeView.BeginUpdate();
            if (documentOutlineTreeView.Tag != (object)guid)
                documentOutlineTreeView.Nodes.Clear();
            parentTreeNode = null;
            treeNodeIndices = new ArrayList();
            treeNodeIndices.Add(0);
            treeNodeChildren = new ArrayList();
            treeNodeChildren.Add(0);
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////
        // PUBLIC PROCEDURES
        /////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Processes the specified root <see cref="ICompilationUnit"/>.
        /// </summary>
        /// <param name="compilationUnit">The <see cref="ICompilationUnit"/> to examine.</param>
        public void Process(ICompilationUnit compilationUnit)
        {
            this.ProcessNode(compilationUnit, compilationUnit);
        }

    }
}
